////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Michiel Helvensteijn                                    //
//                                                                            //
// This file is part of the Mist compiler.                                    //
//                                                                            //
// The Mist compiler is free software: you can redistribute it and/or modif   //
// it under the terms of the GNU General Public License as published by       //
// the Free Software Foundation, either version 3 of the License, or          //
// (at your option) any later version.                                        //
//                                                                            //
// The Mist compiler is distributed in the hope that it will be useful,       //
// but WITHOUT ANY WARRANTY; without even the implied warranty of             //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              //
// GNU General Public License for more details.                               //
//                                                                            //
// You should have received a copy of the GNU General Public License          //
// along with the Mist compiler.  If not, see <http://www.gnu.org/licenses/>. //
////////////////////////////////////////////////////////////////////////////////

#include "options.hpp"

/******************************************************************************
 * Includes                                                                   *
 ******************************************************************************/

#include <iostream>
using std::cin;
using std::cout;

#include <string>
using std::string;

#include <fstream>
using std::ifstream;
using std::ofstream;

#include "assert.hpp"
#include "messages.hpp"
#include "common.hpp"

/******************************************************************************
 * Function Implementations                                                   *
 ******************************************************************************/

void Options::set_options() {
	po::options_description generic("Generic Options");
	generic.add_options()
		("help,h"   , "print this help message")
		("version,v", "print version information")
	;
	
	po::options_description invisible;
	invisible.add_options()
		("input,i", po::value<string>(), "the input file (- for stdin)")
	;
	
	po::options_description inout("Input / Output Options");
	inout.add_options()
		(      "output,o"   , po::value<string>()                    , "the output file (- for stdout)"           )
		(        "echo,e"                                            , "echo the input file"                      )
		(         "ast,a"                                            , "output the abstract syntax tree"          )
		(         "cpp,c"                                            , "output the C++ code"                      )
		(   "no-output,n"                                            , "generate no output, only check for errors")
		("symbol-table,s"                                            , "output the symboltable"                   )
		("output-tab-size,T", po::value<unsigned>()->default_value(4), "the tabsize to be used in the output"     )
	;
	
	po::options_description messages("Message Options");
	inout.add_options()
		("no-messages,N", "report no errors or warnings")
	;
	
	_options.reset(new po::options_description);
	_options->add(generic).add(inout).add(invisible);
	
	_visibleOptions.reset(new po::options_description);
	_visibleOptions->add(generic).add(inout);
	
	_positional.reset(new po::positional_options_description);
	_positional->add("input", -1);
}

Options::Options() : _parsed(false), _versionAsked(false), _helpAsked(false), _input(NULL), _output(NULL), _inputFile(""), _outputFile(""), _outputType(0x00), _sourceTabSize(1), _outputTabSize(4), _reportMessages(true) {
	set_options();
}

Options::~Options() {
	if (_input != NULL && _input != &cin) {
		static_cast<ifstream*>(_input)->close();
		delete _input;
	}
	if (_output != NULL && _output != &cout) {
		static_cast<ofstream*>(_output)->close();
		delete _output;
	}
}

void Options::parse(int argc, char* argv[], shared_ptr<Messages> messages) {
	po::variables_map vm;
	
	try {
		po::store(po::command_line_parser(argc, argv).options(*_options).positional(*_positional).run(), vm);
		po::notify(vm);
	} catch (po::multiple_occurrences& e) {
		messages->add( Error, "There is an option with multiple occurrences." );
		return;
	} catch (po::ambiguous_option& e) {
		string error = string(e.what()) + " could be " + implode(e.alternatives, ", ", " or ");
		messages->add( Error, error );
		return;
	} catch (po::error& e) {
		messages->add( Error, e.what() );
		return;
	}
	
	_helpAsked      = (vm.count("help")         > 0);
	_versionAsked   = (vm.count("version")      > 0);
	_reportMessages = (vm.count("no-messages") == 0);
	
	if (!_helpAsked && !_versionAsked) {
		if (vm.count("input") == 0) {
			messages->add( Error, "No input source was specified." );
		} else if (vm["input"].as<string>() == "-") {
			_input = &cin;
		} else {
			_inputFile = vm["input"].as<string>();
			_input = new ifstream(_inputFile.c_str());
		}
		
		if (vm.count("no-output") == 0) {
			if (vm.count("output") > 0) {
				if (vm["output"].as<string>() == "-") {
					_output = &cout;
				} else {
					_outputFile = vm["output"].as<string>();
					_output = new ofstream(_outputFile.c_str());
				}
			} else if (_input != NULL && _input != &cin) {
				_outputFile = _inputFile + ".out";
				_output = new ofstream(_outputFile.c_str());
			} else {
				messages->add( Error, "No output target was specified." );
			}
			
			_outputType |= (vm.count("echo")         > 0) * OutputEcho;
			_outputType |= (vm.count("ast")          > 0) * OutputAST;
			_outputType |= (vm.count("symbol-table") > 0) * OutputSymbolTable;
			_outputType |= (vm.count("cpp")          > 0) * OutputCPP;
			
			// In the future, the output will be to compiled code by default,
			// but for now, not specifying an output type is an error
			if (_outputType == 0x00)
				_outputType |= OutputCPP;
		}
		
		_outputTabSize = vm["output-tab-size"].as<unsigned>();
	}
	
	if (messages->count(Error) > 0)
		_reportMessages = true;
	
	_parsed = true;
}

void Options::report_help(ostream& out) const {
	out << "Usage: compiler [options] input-file   (- for stdin)" << endl
	    << *_visibleOptions;
}

bool Options::version_asked() const {
	assert(_parsed);
	
	return _versionAsked;
}

bool Options::help_asked() const {
	assert(_parsed);
	
	return _helpAsked;
}

istream& Options::input() const {
	assert(_parsed);
	
	return *_input;
}

ostream& Options::output() const {
	assert(_parsed);
	
	return *_output;
}

const string& Options::input_file() const {
	assert(_parsed);
	
	return _inputFile;
}

unsigned Options::output_type() const {
	assert(_parsed);
	
	return _outputType;
}

unsigned Options::output_tab_size() const {
	assert(_parsed);
	
	return _outputTabSize;
}

bool Options::report_messages() const {
	return _reportMessages;
}
